home *** CD-ROM | disk | FTP | other *** search
Text File | 1993-02-10 | 31.0 KB | 1,117 lines |
- /*
- * Program: Distributed Electronic Mail Manager (ReadWindow object)
- *
- * Author: Mark Crispin/Mike Dixon, Xerox PARC
- * Networks and Distributed Computing
- * Computing & Communications
- * University of Washington
- * Administration Building, AG-44
- * Seattle, WA 98195
- * Internet: MRC@CAC.Washington.EDU
- *
- * Date: 24 February 1989
- * Last Edited: 11 February 1993
- *
- * Copyright 1993 by the University of Washington
- *
- * Permission to use, copy, modify, and distribute this software and its
- * documentation for any purpose and without fee is hereby granted, provided
- * that the above copyright notice appears in all copies and that both the
- * above copyright notice and this permission notice appear in supporting
- * documentation, and that the name of the University of Washington not be
- * used in advertising or publicity pertaining to distribution of the software
- * without specific, written prior permission. This software is made
- * available "as is", and
- * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
- * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
- * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
- * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
- * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
- * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- *
- */
-
-
- #import "MailManager.h"
- #import "XTextPkg.h" // mdd
-
- @implementation ReadWindow
-
- // Create a new ReadWindow
-
- + new
- {
- self = [super new]; // instantiate ourselves
- [NXApp loadNibSection:"ReadWindow.nib" owner:self withNames:NIL];
- handle = NIL; // no handle initially
- sequence = NIL; // no sequence initially
- return self;
- }
-
-
- // Note our messageView
-
- - setMessageView:anObject
- {
- NXRect frame; // note our view and get its frame
- [(messageView = [anObject docView]) getFrame:&frame];
- [messageView setInitialAction: read_action]; // mdd
- // reset its font
- [messageView renewFont:defaultfont text:"" frame:&frame tag:0];
- [messageView setEditable:NO]; // mdd
- return self;
- }
-
-
- // Note our window
-
- - setWindow:anObject
- {
- window = anObject; // note our window
- [window setDelegate:self]; // we're the window's delegate
- return self;
- }
-
- // Non-interface methods
-
-
- // Note calling window message items view
-
- - setMessageItems:anObject
- {
- messageItems = anObject; // note caller's message items view
- return self;
- }
-
-
- // Note our MAIL handle
-
- - setHandle:(MAILHANDLE *) h
- {
- mail_free_handle (&handle); // flush any old handle
- handle = h; // note MAIL handle
- return self;
- }
-
- // Set message sequence
-
- - setSequence:(SEQUENCE *) seq
- {
- SEQUENCE *s;
- if (s = sequence) { // find head of old sequence list
- while (s->previous) s = s->previous;
- do {
- mail_free_lelt (&s->lelt);// flush the cache entry in case nuked
- sequence = s->next; // get the next in the list
- fs_give ((void **) &s); // now free that sequence
- } while (s = sequence); // run down the sequence list
- }
- return [self moveSequence:seq];
- }
-
-
- // Move message sequence
-
- - moveSequence:(SEQUENCE *) seq
- {
- int m;
- id brw;
- if (sequence = seq) { // set new sequence, update display if non-NIL
- if (lockedread && // highlight browser message if locked read
- (seq->lelt->elt.msgno == [[(brw = [messageItems docView])
- cellAt:(m = seq->lelt->elt.msgno - 1) :0] msgno]))
- [[brw selectCellAt:m :0] scrollCellToVisible: m :0];
- [self updateTitle]; // show the first message's title
- [self displayMessage:self]; // now display the message
- [self updateTitle]; // update the window's title now that read
- [window makeKeyAndOrderFront:self]; // reopen window in case taken out
- // mdd: make key to enable keys
- }
- return self;
- }
-
- // Update window title after action taken to change status
-
- - updateTitle
- {
- char text[TMPLEN];
- MAILSTREAM *stream = mail_stream (handle);
- // stream and message must be valid
- if (stream && sequence->lelt->elt.msgno) {
- // get the window's title
- headerline (text,stream,&sequence->lelt->elt);
- [window setTitle:text]; // set the title
- // update primary window
- [getstreamprop (stream)->window updateTitle];
- }
- // update Flagged switch
- [flagged setIntValue:sequence->lelt->elt.flagged];
- // update Deleted switch
- [deleted setIntValue:sequence->lelt->elt.deleted];
- // update Seen switch
- [seen setIntValue:sequence->lelt->elt.seen];
- // update Answered switch
- [answered setIntValue:sequence->lelt->elt.answered];
- return self;
- }
-
- // Message display operations
-
-
- // Note our display
-
- - setDisplay:anObject
- {
- // set according to the default
- [(display = anObject) selectCellAt:literaldisplay :0];
- return self;
- }
-
-
- // Note attachment panel
-
- - setAttachmentPanel:anObject
- {
- attachmentPanel = anObject; // note attachment panel
- return self;
- }
-
-
- // Note attachment browser
-
- - setAttachmentView:anObject
- {
- NXSize size;
- NXRect frame;
- [anObject getFrame:&frame]; // get frame of attachments view
- // create a scroll view
- [(attachmentView = [ScrollView newFrame:&frame]) setBorderType:NX_BEZEL];
- // only vertical scrolling is required
- [attachmentView setVertScrollerRequired:T];
- // allow its size to change
- [attachmentView setAutosizing:NX_HEIGHTSIZABLE|NX_WIDTHSIZABLE];
- // put up the view
- [[anObject superview] replaceSubview:anObject with:attachmentView];
- // get the size minus the scroll bars
- [attachmentView getContentSize:&size];
- // set it as such in the browser frames
- NX_WIDTH (&frame) = size.width = 1300.0; NX_HEIGHT (&frame) = size.height;
- // create the browser
- browser = [Matrix newFrame:&frame mode:NX_RADIOMODE
- cellClass:[ActionCell class] numRows:0 numCols:0];
- size.height = 13.0; // large enough to hold the text
- [browser setCellSize:&size]; // set size of each cell
- size.width = size.height = 0; // no intercell spacing
- [browser setIntercell:&size];
- [browser setAutoscroll:T]; // make it autoscrolling
- [[browser setAction:@selector(doAttachment:)] setTarget:self];
- // put the browser in the scroller and display
- [[attachmentView setDocView:browser] display];
- return self;
- }
-
- // Here is where we actually display a message
-
- - displayMessage:sender
- {
- char *s;
- NXRect fr;
- int m = sequence->lelt->elt.msgno;
- int lit = [display selectedRow];
- MAILSTREAM *stream = mail_stream (handle);
- ENVELOPE *env = sequence->lelt->env;
- BODY *body = sequence->lelt->body;
- if (stream && m) { // must have open stream and not be expunged
- // get frame, don't update display until ready
- [[messageView getFrame:&fr] setAutodisplay:NIL];
- // initialize view
- [[messageView renewFont:defaultfont text:"" frame:&fr tag:0] setSel:0 :0];
- // try getting body now if don't have it yet
- if ((!lit) && !body) env = mail_fetchstructure (stream,m,&body);
- // not literal, have body, body complex
- if ((!lit) && body && (body->type != TYPETEXT)) {
- // yes, resize browser for # of attachments
- [[browser renewRows:[self countAttachments:body] cols:1] sizeToCells];
- [self displayAttachment:body prefix:NIL index:0 row:0];
- [browser display]; // display attachments
- [[attachmentPanel attachWindow:window] orderFront:self];
- // open header in case non-textual attachment
- [[messageView setSel:0 :0] replaceSel:s = filtered_header (env)];
- fs_give ((void **) &s); // done with header string
- // do first attachment
- [[browser selectCellAt:0 :0] sendAction];
- }
- else { // simple message has no attachments
- [attachmentPanel orderOut:self];
- if (lit || !body) {
- [[messageView setSel:0 :0] replaceSel:s = lit ?
- fixnl (cpystr (mail_fetchheader (stream,m))) : filtered_header (env)];
- fs_give ((void **) &s); // done with header string
- [messageView replaceSel:s = fixnl (cpystr (mail_fetchtext(stream,m)))];
- fs_give ((void **) &s); // done with text string
- }
- else { // fake as if had attachments
- unsigned long len;
- s = mail_fetchbody (stream,m,"1",&len);
- [self displayText:(unsigned char *) s length:len type:body->type
- subtype:body->subtype encoding:body->encoding];
- }
- }
- // put cursor at start, display text
- [[[messageView setSel:0 :0] display] setAutodisplay:T];
- }
- return self;
- }
-
- // Display text in the view
-
- - displayText:(unsigned char *) s length:(unsigned long) len type:(int) type
- subtype:(char *) sub encoding:(int) enc
- {
- char *t;
- void *f = NIL;
- NXRect fr;
- ENVELOPE *env = sequence->lelt->env;
- [messageView getFrame:&fr]; // initialize view
- [messageView renewFont:defaultfont text:"" frame:&fr tag:0];
- // install header
- [[messageView setSel:0 :0] replaceSel:t = filtered_header (env)];
- fs_give ((void **) &t); // flush temp
- if (!s) return self;
- switch (enc) { // if text
- case ENCBASE64: // 3 in 4 encoding -- someone may use this
- f = s = rfc822_base64 (s,len,&len);
- break;
- case ENCQUOTEDPRINTABLE: // readable 8-bit
- f = s = (unsigned char *) fixnl ((char *) rfc822_qprint (s,len,&len));
- break;
- case ENC7BIT: // 7-bit text
- case ENC8BIT: // 8-bit text
- f = s = (unsigned char *) fixnl (cpystr ((char *) s));
- break;
- case ENCBINARY: // binary
- default: // everything else
- break;
- }
- // Should do something about RichText here...
- // display the text
- [messageView replaceSel:s ? (char *) s :
- "Undecodable text -- use literal to display it"];
- if (f) fs_give ((void **) &f);// flush temp
- return self;
- }
-
-
- // Count non-multipart body parts
-
- - (int) countAttachments:(BODY *) body
- {
- int i = 0;
- PART *part;
- // depending upon body type
- if (body) switch (body->type) {
- case TYPEMULTIPART: // multiple parts
- for (part = body->contents.part; part; part = part->next)
- i += [self countAttachments:&part->body];
- break;
- case TYPEMESSAGE: // encapsulated message
- i = [self countAttachments:body->contents.msg.body];
- default: // all other types have one part
- i++;
- break;
- }
- return i; // return number of body parts
- }
-
- // Display an attachment in the browser
-
- - (int) displayAttachment:(BODY *) body prefix:(char *) pfx index:(int) i
- row:(int) r
- {
- char tmp[TMPLEN];
- char *s = tmp;
- PARAMETER *par;
- PART *part; // multipart doesn't have a row to itself
- if (body->type == TYPEMULTIPART) {
- // if not first time, extend prefix
- if (pfx) sprintf (tmp,"%s%d.",pfx,++i);
- else tmp[0] = '\0';
- for (i = 0,part = body->contents.part; part; part = part->next)
- r = [self displayAttachment:&part->body prefix:tmp index:i++ row:r];
- }
- else { // non-multipart, output oneline descriptor
- if (!pfx) pfx = ""; // dummy prefix if top level
- sprintf (s,"%s%d %s",pfx,++i,body_types[body->type]);
- if (body->subtype) sprintf (s += strlen (s),"/%s",body->subtype);
- if (body->description) sprintf (s += strlen (s)," (%s)",body->description);
- if (par = body->parameter) do
- sprintf (s += strlen (s),";%s=%s",par->attribute,par->value);
- while (par = par->next);
- if (body->id) sprintf (s += strlen (s),", id = %s",body->id);
- switch (body->type) { // bytes or lines depending upon body type
- case TYPEMESSAGE: // encapsulated message
- case TYPETEXT: // plain text
- sprintf (s += strlen (s)," (%lu lines)",body->size.lines);
- break;
- default:
- sprintf (s += strlen (s)," (%lu bytes)",body->size.bytes);
- break;
- }
- [[[browser cellAt:r++ :0] setStringValue:tmp] setTag:(int) body];
- // encapsulated message?
- if (body->type == TYPEMESSAGE && (body = body->contents.msg.body)) {
- if (body->type == TYPEMULTIPART)
- r = [self displayAttachment:body prefix:pfx index:i-1 row:r];
- else { // build encapsulation prefix
- sprintf (tmp,"%s%d.",pfx,i);
- r = [self displayAttachment:body prefix:tmp index:0 row:r];
- }
- }
- }
- return r;
- }
-
- // Process an attachment
-
- - doAttachment:sender
- {
- int i,j;
- char tmp[TMPLEN];
- NXStream *file = NIL;
- id cell = [browser selectedCell];
- char *t;
- char *s = (char *) [cell stringValue];
- BODY *b = (BODY *) [cell tag];
- PARAMETER *par;
- int m = sequence->lelt->elt.msgno;
- unsigned long len = strchr (s,' ') - s;
- MAILSTREAM *stream = mail_stream (handle);
- SavePanel *ds = [SavePanel new];
- strcpyn (tmp,s,len); // get attachment ID
- tmp[len] = '\0';
- if (stream && m && (s = mail_fetchbody (stream,m,tmp,&len))) switch(b->type){
- case TYPEAUDIO: // audio -- hope it's something we can grok!
- if (!(file = [self attachmentStream:(unsigned char *) s length:len
- encoding:b->encoding]))
- mm_log ("Can't open memory stream for audio",ERROR);
- else { // make name for sound
- strcpy (tmp,"/tmp/audioXXXXXX.snd");
- // sniff at data
- NXGetMemoryBuffer (file,&s,&i,&j);
- // look like one of our sound files?
- if (strcmp (b->subtype,"BASIC") && (i > 5) &&
- (*(unsigned long *) s) == SND_MAGIC)
- NXSaveToFile (file,NXGetTempFilename (tmp,strchr (tmp,'X') - tmp));
- // ugh, assume we can win by prepending header
- else if ((j = open (tmp,O_WRONLY|O_CREAT|O_EXCL,0600)) >= 0) {
- SNDSoundStruct snd;
- snd.magic = SND_MAGIC; // build header
- snd.dataLocation = sizeof (SNDSoundStruct);
- snd.dataSize = j;
- snd.dataFormat = SND_FORMAT_MULAW_8;
- snd.samplingRate = SND_RATE_CODEC;
- snd.channelCount = 1;
- snd.info[0] = snd.info[1] = snd.info[2] = snd.info[3] = 0;
- write (j,(char *) &snd,sizeof (SNDSoundStruct));
- write (j,s,i);
- close (j);
- }
- if (m = SNDPlaySoundfile (tmp,0))
- NXRunAlertPanel ("Audio playback failed",SNDSoundError(m),NIL,NIL,NIL);
- unlink (tmp); // flush the file
- if ((m || ([cell mouseDownFlags] & NX_SHIFTMASK)) &&
- [[ds setTitle:"Save audio"] runModal] &&
- NXSaveToFile (file,(char *) [ds filename]))
- mm_log ("Can't write audio",ERROR);
- NXCloseMemory (file,NX_FREEBUFFER);
- }
- break;
-
- case TYPEAPPLICATION: // random application data
- if (strcmp (b->subtype,"POSTSCRIPT")) {
- // not PostScript
- if (!(file = [self attachmentStream:(unsigned char *) s length:len
- encoding:b->encoding]))
- mm_log ("Can't open memory stream for binary attachment",ERROR);
- else { // set up prompt based on type of data
- sprintf (tmp,"Save %s data",b->subtype);
- if (!strcmp (b->subtype,"OCTET-STREAM")) {
- for (t = NIL,par = b->parameter; par && !t; par = par->next)
- if (!strcmp (par->attribute,"TYPE"))
- sprintf (tmp,"Save binary %s data",t = par->value);
- }
- else if (!strcmp (b->subtype,"ODA")) {
- for (t = NIL,par = b->parameter; par && !t; par = par->next)
- if (!strcmp (par->attribute,"PROFILE"))
- sprintf (tmp,"Save ODA profile %s data",t = par->value);
- }
- [ds setTitle:tmp]; // set prompt string
- // get suggested filename
- for (t = NIL,par = b->parameter; par && !t; par = par->next)
- if (!strcmp (par->attribute,"NAME")) t = par->value;
- // excise directory parths
- if (t && strchr (t,'/')) t = strrchr (t,'/') + 1;
- if ((t ? [ds runModalForDirectory:[ds directory] file:t] :
- [ds runModal]) && NXSaveToFile (file,(char *) [ds filename]))
- mm_log ("Can't write binary attachment",ERROR);
- NXCloseMemory (file,NX_FREEBUFFER);
- }
- break;
- }
- // fall through into IMAGE if PostScript
-
- case TYPEIMAGE: // static image
- if (!(file = [self attachmentStream:(unsigned char *) s length:len
- encoding:b->encoding]))
- mm_log ("Can't open memory stream for image",ERROR);
- else { // try to run program to display subtype
- id workspace = [Application workspace];
- // make filename with subtype as extension
- sprintf (tmp,"/tmp/imageXXXXXX.%s",strcmp (b->subtype,"POSTSCRIPT") ?
- lcase (strcpy (tmp+100,b->subtype)) : "ps");
- // save data to file
- NXSaveToFile (file,NXGetTempFilename (tmp,strchr (tmp,'X') - tmp));
- // see if file type known to WorkSpace
- i = [workspace getInfoForFile:tmp application:&s type:&t];
- // run app if valid, have app, and app not Edit
- if ((i != NO) && s && strcmp (s,"Edit"))i = [workspace openTempFile:tmp];
- else i = NO; // make sure we see it as a failure
- if (i == NO) { // lost?
- NXRunAlertPanel (NIL,"Unable to display image of type %s",
- NIL,NIL,NIL,b->subtype);
- unlink (tmp); // flush the file
- }
- // does user want to save it explicitly?
- if ((i == NO) || ([cell mouseDownFlags] & NX_SHIFTMASK)) {
- sprintf (tmp,"Save image/%s",b->subtype);
- if ([[ds setTitle:tmp] runModal] &&
- NXSaveToFile (file,(char *) [ds filename]))
- mm_log ("Can't write image attachment",ERROR);
- }
- // close memory file
- NXCloseMemory (file,NX_FREEBUFFER);
- }
- break;
-
- case TYPEMESSAGE: // encapsulated message
- case TYPETEXT: // plain text -- display in view
- [self displayText:(unsigned char *) s length:len type:b->type
- subtype:b->subtype encoding:b->encoding];
- break;
- case TYPEVIDEO: // video
- default: // random
- if (!(file = [self attachmentStream:(unsigned char *) s length:len
- encoding:b->encoding]))
- mm_log ("Can't open memory stream for attachment",ERROR);
- else {
- sprintf (tmp,"Save %s",body_types[b->type]);
- if ([[ds setTitle:tmp] runModal] &&
- NXSaveToFile (file,(char *) [ds filename]))
- mm_log ("Can't write attachment",ERROR);
- NXCloseMemory (file,NX_FREEBUFFER);
- }
- break;
- }
- return self;
- }
-
- // Return file stream from attachment
-
- - (NXStream *) attachmentStream:(unsigned char *) s length:(unsigned long) len
- encoding:(int) enc
- {
- NXStream *file = NXOpenMemory (NIL,0,NX_READWRITE);
- if (file) { // only if we won
- switch (enc) { // decode as necessary
- case ENCBASE64: // 3 in 4 encoding -- most likely case
- s = rfc822_base64 (s,len,&len);
- NXWrite (file,s,len); // write the binary
- fs_give ((void **) &s);
- break;
- case ENCQUOTEDPRINTABLE: // 8-bit text
- s = rfc822_qprint (s,len,&len);
- NXWrite (file,s,len); // write the 8-bit text
- fs_give ((void **) &s);
- break;
- default: // other cases
- NXWrite (file,s,len); // just write the data
- break;
- }
- NXFlush (file); // make sure all data written
- }
- return file; // return the file
- }
-
- // We are ordered to close
-
- - close
- {
- [self windowWillClose:self]; // do cleanup
- [window close]; // close our window
- window = NIL;
- return self;
- }
-
-
- // Set FreeWhenClosed state for our window
-
- - setFreeWhenClosed:(BOOL) flag
- {
- // set the state
- [window setFreeWhenClosed:flag];
- return self;
- }
-
-
- // Window is closing
-
- - windowWillClose:sender
- {
- [attachmentPanel close]; // nuke attachment panel
- attachmentPanel = NIL;
- [self setSequence:NIL]; // flush the sequence
- [self setHandle:NIL]; // flush the handle
- return self;
- }
-
-
- // Window moved
-
- - windowDidMove:sender
- {
- // make attachment panel track this window
- [attachmentPanel attachWindow:window];
- return self;
- }
-
- // Miscellaneous and message motion buttons
-
-
- // Open the help panel
-
- - help:sender
- {
- [NXApp readHelp:sender];
- return self;
- }
-
-
- // Print message
-
- - print:sender
- {
- // print what's in the message view
- [messageView printPSCode:self];
- return self;
- }
-
-
- // Kill button - delete this message, move to next
-
- - kill:sender
- {
- [deleted setIntValue:T]; // mark that we want deletion
- [self delete:self]; // now delete the message
- // move to next message, quit if at end
- if ((![self next:self]) && closefinalkill) [self close];
- return self;
- }
-
- // Next button - move to next message
-
- - next:sender
- {
- int i;
- MAILSTREAM *stream = mail_stream (handle);
- SEQUENCE *seq = sequence;
- if (stream) {
- while (seq->next) { // if there's a next
- // if not expunged we can use it
- if (seq->next->lelt->elt.msgno) break;
- else seq = seq->next; // otherwise skip over it
- }
- // if have a useful message use it
- if (seq->next) [self moveSequence:seq->next];
- else {
- // get this message number
- i = sequence->lelt->elt.msgno;
- // if single message sequence, not expunged,
- // and not at end of mail file
- if (sequence->previous || (i == 0) || (i >= stream->nmsgs)) {
- mm_log ("No next message",WARN);
- return NIL;
- }
- else { // we can do motion within the mail file
- // don't need this elt any more
- mail_free_lelt (&sequence->lelt);
- // move to next message
- sequence->lelt = mail_lelt (stream,++i);
- // lock down this elt
- if (!++(sequence->lelt->elt.lockcount))
- fatal ("Elt lock count overflow");
- // now move to that message
- [self moveSequence:sequence];
- }
- }
- }
- return self;
- }
-
- // Previous button - move to previous message
-
- - previous:sender
- {
- int i;
- MAILSTREAM *stream = mail_stream (handle);
- SEQUENCE *seq = sequence;
- if (stream) {
- while (seq->previous) { // if there's a previous
- // if not expunged we can use it
- if (seq->previous->lelt->elt.msgno) break;
- else seq = seq->previous; // otherwise skip over it
- }
- // if have a useful message use it
- if (seq->previous) [self moveSequence:seq->previous];
- else {
- // get this message number
- i = sequence->lelt->elt.msgno;
- // if single message sequence, not expunged,
- // and not at top of mail file
- if (sequence->next || (i <= 1)) mm_log ("No previous message",WARN);
- else { // we can do motion within the mail file
- // don't need this elt any more
- mail_free_lelt (&sequence->lelt);
- // move to previous message
- sequence->lelt = mail_lelt (stream,--i);
- // lock down this elt
- if (!++(sequence->lelt->elt.lockcount))
- fatal ("Elt lock count overflow");
- // now move to that message
- [self moveSequence:sequence];
- }
- }
- }
- return self;
- }
-
- // System flag switches
-
-
- // Note Answered switch
-
- - setAnswered:anObject;
- {
- answered = anObject; // note Answered
- return self;
- }
-
-
- // Note Flagged switch
-
- - setFlagged:anObject;
- {
- flagged = anObject; // note Flagged
- return self;
- }
-
-
- // Note Deleted switch
-
- - setDeleted:anObject;
- {
- deleted = anObject; // note Deleted
- return self;
- }
-
-
- // Note Seen switch
-
- - setSeen:anObject;
- {
- seen = anObject; // note Seen
- return self;
- }
-
- // Answered switch - set the answered status
-
- - answer:sender
- {
- char seq[10];
- MAILSTREAM *stream = mail_stream (handle);
- // stream and message must be valid
- if (stream && sequence->lelt->elt.msgno) {
- // make string form
- sprintf (seq,"%lu",sequence->lelt->elt.msgno);
- // only allow clearing this
- if (![answered intValue]) mail_clearflag (stream,seq,"\\Answered");
- }
- return [self updateTitle];
- }
-
- // mdd: need a method to delete/undelete a message
- // mode is 0=undelete; 1=delete; 2=toggle
-
- - deleteMe:(int)mode
- {
- BOOL val = ((mode == 2) ? ![deleted intValue] : mode);
-
- [deleted setIntValue:val];
- return [self delete:self];
- }
-
- // Deleted switch - set the deleted status
-
- - delete:sender
- {
- char seq[10];
- MAILSTREAM *stream = mail_stream (handle);
- // stream and message must be valid
- if (stream && sequence->lelt->elt.msgno) {
- // make string form
- sprintf (seq,"%lu",sequence->lelt->elt.msgno);
- // set state per the switch
- if ([deleted intValue]) mail_setflag (stream,seq,"\\Deleted");
- else mail_clearflag (stream,seq,"\\Deleted");
- }
- return [self updateTitle];
- }
-
- // Flag switch - set the flagged status
-
- - flag:sender
- {
- char seq[10];
- MAILSTREAM *stream = mail_stream (handle);
- // stream and message must be valid
- if (stream && sequence->lelt->elt.msgno) {
- // make string form
- sprintf (seq,"%lu",sequence->lelt->elt.msgno);
- // set state per the switch
- if ([flagged intValue]) mail_setflag (stream,seq,"\\Flagged");
- else mail_clearflag (stream,seq,"\\Flagged");
- }
- return [self updateTitle];
- }
-
-
- // Seen switch - set the seen status
-
- - mark:sender
- {
- char seq[10];
- MAILSTREAM *stream = mail_stream (handle);
- // stream and message must be valid
- if (stream && sequence->lelt->elt.msgno) {
- // make string form
- sprintf (seq,"%lu",sequence->lelt->elt.msgno);
- // set state per the switch
- if ([seen intValue]) mail_setflag (stream,seq,"\\Seen");
- else mail_clearflag (stream,seq,"\\Seen");
- }
- return [self updateTitle];
- }
-
- // Keyword operations
-
-
- // Note our Keyword button
-
- - setKeyword:anObject
- {
- id l = [PullOutMenu new:(keyword = anObject)];
- [l addItem:"Keyword..." action:@selector(keywordSet:)];
- [l addItem:"Clear Keyword..." action:@selector(keywordClear:)];
- return self;
- }
-
-
- // Set the selected keywords in selected messages
-
- - keywordSet:sender
- {
- return [self keyword:sender set:T];
- }
-
-
- // Clear the selected keywords in selected messages
-
- - keywordClear:sender
- {
- return [self keyword:sender set:NIL];
- }
-
- // Change keywords
-
- - keyword:sender set:(BOOL) set
- {
- char seq[10];
- char *keys;
- MAILSTREAM *stream = mail_stream (handle);
- // stream and message must be valid
- if (stream && sequence->lelt->elt.msgno &&
- (keys = [(getstreamprop (stream))->window getKeywords])) {
- // make string form of this sequence
- sprintf (seq,"%lu",sequence->lelt->elt.msgno);
- // set the flag, if we can
- if (set) mail_setflag (stream,seq,keys);
- else mail_clearflag (stream,seq,keys);
- fs_give ((void **) &keys);
- }
- return [self updateTitle]; // update title with new status
- }
-
- // File operations
-
-
- // Note our Copy button
-
- - setCopy:anObject
- {
- id l = [PullOutMenu new:(copy = anObject)];
- [l addItem:"Copy..." action:@selector(fileCopy:)];
- [l addItem:"Move..." action:@selector(fileMove:)];
- return self;
- }
-
-
- // Copy message to another mailbox
-
- - fileCopy:sender
- {
- return [self copy:sender delete:NIL];
- }
-
-
- // Move (copy + delete) message to another mailbox
-
- - fileMove:sender
- {
- [self copy:sender delete:T];
- if ((![self next:self]) && closefinalkill) [self close];
- return self;
- }
-
- // Do the copy or move
-
- - copy:sender delete:(BOOL) del
- {
- char seq[10];
- char *dest;
- FILE *file;
- unsigned char *text;
- LONGCACHE *lelt = sequence->lelt;
- MAILSTREAM *stream = mail_stream (handle);
- // get file
- if (stream && lelt->elt.msgno && (dest = [NXApp getFile])) {
- if (*dest == '*') { // want local copy
- if (!(file = fopen (dest+1,"a")))
- NXRunAlertPanel ("Open failed","Can't open local file",NIL,NIL,NIL);
- else { // blat it to the file
- text = (unsigned char *)
- cpystr (mail_fetchtext (stream,lelt->elt.msgno));
- append_msg (file,lelt->env ? lelt->env->sender : NIL,&lelt->elt,
- mail_fetchheader (stream,lelt->elt.msgno),text);
- fs_give ((void **) &text);
- fclose (file); // close off the file
- if (del) { // if called by move:
- // mark for deletion
- [deleted setIntValue:T];
- [self delete:self]; // now delete the message
- }
- }
- }
- else { // want remote copy, make string form of msgno
- sprintf (seq,"%lu",lelt->elt.msgno);
- // now do the copy
- if (del) mail_move (stream,seq,dest+1);
- else mail_copy (stream,seq,dest+1);
- }
- fs_give ((void **) &dest); // flush the destination file string
- }
- return [self updateTitle];
- }
-
- // Message forwarding
-
-
- // Note our Forward button
-
- - setForward:anObject;
- {
- id l = [PullOutMenu new:(forward = anObject)];
- [l addItem:"Forward..." action:@selector(forward:)];
- [l addItem:"Remail..." action:@selector(remail:)];
- return self;
- }
-
-
- // Forward button - forward a message
-
- - forward:sender
- {
- char tmp[TMPLEN],t[TMPLEN];
- char *s;
- id compose;
- id body;
- int m = sequence->lelt->elt.msgno;
- ENVELOPE *msg;
- ENVELOPE *env = sequence->lelt->env;
- MAILSTREAM *stream = mail_stream (handle);
- if (stream && m) { // have to have a stream to do this
- mail_fetchfrom (t,stream,m,FROMLEN);
- // tie off trailing spaces
- for (s = t+FROMLEN-2; *s == ' '; --s) *s = '\0';
- sprintf (tmp,"[%s: %s]",t,(env && env->subject) ?
- env->subject : "(forwarded message)");
- compose = [NXApp compose:self];
- msg = [compose msg];
- msg->subject = cpystr (tmp);// set up Subject
- [compose updateEnvelopeView];
- body = [compose bodyView]; // get the message text body view
- [body selectAll:sender]; // prepare to initialize text
- [body replaceSel:"\n ** Begin Forwarded Message **\n\n"];
- // insert message
- [body replaceSel:(s = ([display selectedRow] || !env) ?
- fixnl (cpystr (mail_fetchheader (stream,m))) : filtered_header (env))];
- fs_give ((void **) &s);
- [body replaceSel:(s = fixnl (cpystr (mail_fetchtext (stream,m))))];
- fs_give ((void **) &s); // don't need space any more
- [body setSel:0:0]; // put cursor at start of message
- [body display]; // update the display
- [compose selectBody]; // now select the appropriate field
- }
- return self;
- }
-
- // Remail button - remail a message
-
- - remail:sender
- {
- char *s;
- id compose;
- id body;
- int msgno = sequence->lelt->elt.msgno;
- ENVELOPE *msg;
- MAILSTREAM *stream = mail_stream (handle);
- if (stream && msgno) { // have to have a stream and msgno to do this
- compose = [NXApp compose:self];
- msg = [compose msg];
- // get original header
- msg->remail = cpystr (mail_fetchheader (stream,msgno));
- body = [compose bodyView]; // get the message text body view
- [body selectAll:sender]; // prepare to initialize text
- [body replaceSel:(s = fixnl (cpystr (mail_fetchtext (stream,msgno))))];
- fs_give ((void **) &s);
- [body setSel:0:0]; // put cursor at start of message
- [body setEditable:NIL]; // can't change remail text
- [body display]; // update the display
- [compose selectBody]; // now select the appropriate field
- }
- return self;
- }
-
- // Message reply operations
-
-
-
- // Note our Reply button
-
- - setReply:anObject;
- {
- id l = [PullOutMenu new:(reply = anObject)];
- [l addItem:"Reply..." action:@selector(replySender:)];
- [l addItem:"Reply to All..." action:@selector(replyAll:)];
- return self;
- }
-
-
- // Reply to All button - reply to all recipients of message
-
- - replyAll:sender
- {
- return [self reply:sender all:T];
- }
-
-
- // Reply to Sender button - reply to sender of message
-
- - replySender:sender
- {
- return [self reply:sender all:NIL];
- }
-
- // Reply to message
-
- - reply:sender all:(BOOL) all
- {
- char *s,*d;
- int i = [messageView textLength];
- MAILSTREAM *stream = mail_stream (handle);
- d = (s = fs_get (1+i)); // get message we're replying to
- [messageView getSubstring:s start:0 length:i];
- s[i] = '\0'; // tie off string
- // search for double newline
- while (*d) if (*d++ == '\n' && *d == '\n') break;
- if (*d) d++; // skip over the second one if found it
- else d = s; // else use original string
- // create a compose window under it
- [[ReplyWindow new:(stream ? mail_makehandle (stream) : NIL)
- lelt:sequence->lelt text:d all:all] attachWindow:window];
- fs_give ((void **) &s); // flush the text
- return self;
- }
-
-
- @end
-
- // mdd: support for keyboard message movement
-
- @interface XText(ReadWindowText)
- - nextMsg:(BOOL)closeAtEnd;
- - prevMsg;
- - deleteMsg:(int)mode; // mode is 0=undelete; 1=delete; 2=toggle
- @end
-
- static id my_readWindow(id view) {
- id readWindow = [[view window] delegate];
-
- if (readWindow && [readWindow isKindOf:[ReadWindow class]])
- return readWindow;
- else
- return nil;
- }
-
- @implementation XText(ReadWindowText)
-
- - nextMsg:(BOOL)closeAtEnd
- {
- id readWindow = my_readWindow(self);
-
- if (readWindow)
- if ((![readWindow next:self]) && closeAtEnd)
- [readWindow close];
- return self;
- }
-
- - prevMsg
- {
- id readWindow = my_readWindow(self);
-
- if (readWindow)
- [readWindow previous:self];
- return self;
- }
-
- - deleteMsg:(int)mode
- {
- id readWindow = my_readWindow(self);
-
- if (readWindow)
- [readWindow deleteMe:mode];
- return self;
- }
-
- @end
-